If a record failed
to be inserted, nothing stops that record from being lost to us forever.
Yes, you could look in the Application log, but this is tedious and not
a very good solution. Therefore we are going to add some extra
validation, logging, and notification.
Creating Process Error Folder
First we need to create a
directory to store our records that fail to get inserted. In the same
directory as our incoming directory, add a directory called
ProcessError. This is where we will store our records that fail.
You can store the failed records two ways:
Error Processing Solution
Each solution has its pros
and cons. I’m going to use a single file per record because I want to
avoid any locking that could occur if I were to have a secondary
automated process collecting this data or trying to reprocess it.
For each file we will use
a format of fail_record_Guid.fri for failed record information. The
GUID portion of our file assures us that no record will be overwritten.
We could use DateTime.Now.Ticks as an identifier, but with multiple
threads and faster processors running in parallel, it is possible to
overwrite files that you are creating because each thread uses the same
Ticks value.
Updating the FileWorkerOptions Class
We need to add our process file location to our FileWorkerOptions
class so that for each record we create in the database we will know
the location of where to save it. Make the changes shown in Listing 1 to your FileWorkerOptions class to add a property that we can set or retrieve as needed.
Listing 1. ProcessError property implementation.
Private m_ProcessError As String
Public Property ProcessError() As String Get Return m_ProcessError End Get Set(ByVal value As String) m_ProcessError = value End Set End Property
|
Updating Our Configuration.xml File
For each incoming folder we need to configure the ProcessError folder that we will use for it. Listing 2 shows an example of what your file should now look like.
Listing 2. ProcessError modification in configuration.xml file.
<?xml version="1.0" encoding="utf-8" ?> <Configuration> <FileWorkerOptions> <FileWorkerProperties> <IncomingPath>c:\tutorials\incoming</IncomingPath> <OutgoingPath>c:\tutorials\outgoing</OutgoingPath> <ProcessedFolder>c:\tutorials\processed</ProcessedFolder> <FileType>*.txt</FileType> <SmtpServer>testserver</SmtpServer> <Subject>Test Subject</Subject> <Message>Found Message</Message> <Sender>[email protected]</Sender> <Recipient>[email protected]</Recipient> <SmtpPort>25</SmtpPort> <MailEnabled>true</MailEnabled> <ProcessedError>c:\tutorials\processerror</ProcessedError> </FileWorkerProperties> </FileWorkerOptions> </Configuration>
|
You can see that I have added the <ProcessError>
element and set it equal to my newly created folder path. Each record
that fails in my incoming folder or outgoing folder will be written to
the ProcessError folder as a separate file.
Updating the FileWorker Class
We will modify several things from the current FileWorker
implementation to support additional threading, processing, and
notifications. The following sections are required to update the FileWorker class.
Updating the FileWorker Class Constructor
To reflect the support for the new ProcessError property in the FileWorker class, the constructor needs to be updated. In Listing 3, the constructor has been updated to update the local ProcessError property based on the passed in FileWorkerOptions instance.
Listing 3. Updated FileWorker class constructor.
Public Sub New(ByRef threadaction As ThreadActionState, ByVal fileworkeroptions As FileWorkerOptions) m_ThreadAction = threadaction
m_FileWorkerOptions.Input = fileworkeroptions.Input m_FileWorkerOptions.Output = fileworkeroptions.Output m_FileWorkerOptions.FileType = fileworkeroptions.FileType m_FileWorkerOptions.ProcessedPath = fileworkeroptions.ProcessedPath m_FileWorkerOptions.EmailProperties = fileworkeroptions.EmailProperties m_FileWorkerOptions.ProcessError = fileworkeroptions.ProcessError End Sub
|
Updating the <Tutorials.ThreadFunc> Method
Now
that we have created our property to store the location internally and
added the value to our configuration file, we have to link the two
together using the <threadFunc> method.
In your current threadFunc method, add the bolded code shown in Listing 4; the other non-bolded lines should already exist.
Listing 4. <ThreadFunc> XML configuration read changes.
Private Sub ThreadFunc() Try 'Load our Configuration File Dim Doc As XmlDocument = New XmlDocument() Doc.Load(My.Settings.Configuration) Dim Options As XmlNode 'Get a pointer to the Outer Node Options = Doc.SelectSingleNode("//*[local-name()='FileWorkerOptions']") If (Not Options Is Nothing) Then 'Get a pointer to the first child node of FileWorkerOptions Dim tmpOptions As System.Xml.XPath.XPathNavigator _ = Options.FirstChild.CreateNavigator() If (Not tmpOptions Is Nothing) Then Dim FWOptions As New FileWorkerOptions Dim children As System.Xml.XPath.XPathNavigator 'Loop through each childe node (FileWorkerOption) and 'get the values. Create a new FileWorkerOptions instance and 'FileWorkerOption instance Do Try children = tmpOptions.SelectSingleNode("IncomingPath") FWOptions.Input = children.Value
children = tmpOptions.SelectSingleNode("OutgoingPath") FWOptions.Output = children.Value
children = tmpOptions.SelectSingleNode("FileType") FWOptions.FileType = children.Value
children = tmpOptions.SelectSingleNode("ProcessedPath") FWOptions.ProcessedPath = children.Value
children = tmpOptions.SelectSingleNode("ProcessedError") FWOptions.ProcessError = children.Value
Dim tmpDetail As New EmailDetail children = tmpOptions.SelectSingleNode("Message") tmpDetail.Message = children.Value
children = tmpOptions.SelectSingleNode("Subject") tmpDetail.Subject = children.Value children = tmpOptions.SelectSingleNode("Sender") tmpDetail.Sender = children.Value
children = tmpOptions.SelectSingleNode("Recipient") tmpDetail.Recipient = children.Value
children = tmpOptions.SelectSingleNode("SmtpPort") tmpDetail.SmtpPort = children.Value
children = tmpOptions.SelectSingleNode("SmtpServer") tmpDetail.SmtpServer = children.Value
FWOptions.EmailProperties = tmpDetail
Dim tmpFW As New FileWorker(m_ThreadAction, FWOptions)
children = tmpOptions.SelectSingleNode("MailEnabled") tmpFW.MailEnabled = Convert.ToBoolean(children.Value)
m_WorkerThreads.Add(tmpFW) tmpFW.Start() Catch ex As Exception WriteLogEvent(ex.ToString(), CONFIG_READ_ERROR, EventLogEntryType.Error, _ My.Resources.Source) End Try Loop While (tmpOptions.MoveToNext) End If End If Catch ex As Exception WriteLogEvent(ex.ToString(), ONSTART_ERROR, _ EventLogEntryType.Error, My.Resources.Source) Me.Stop() End Try End Sub
|
Now that we have successfully retrieved and set our property value we need to make the code functional.